listbase: Compute rubberband region on-demand
authorBenjamin Otte <otte@redhat.com>
Fri, 26 Jun 2020 00:56:38 +0000 (02:56 +0200)
committerBenjamin Otte <otte@redhat.com>
Fri, 26 Jun 2020 05:13:32 +0000 (07:13 +0200)
Instead of storing the active items as we go, compute the affected items
whenever the rubberband changes and in particular when the rubberband
ends.
That way, the rubberband is guaranteed to select a rectangle even
after scrolling very far.

This is achieved by having a get_items_in_rect() vfunc that selects all
the items in the rubberbanded rectangle and returns them as a bitset.

gtk/gtkgridview.c
gtk/gtklistbase.c
gtk/gtklistbaseprivate.h
gtk/gtklistview.c

index ac3d49cddae07d543e558864246cb39b8a558e59..3a1b96098e7cb9d231a6e309827696d40f2fe15b 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "gtkgridview.h"
 
+#include "gtkbitset.h"
 #include "gtkintl.h"
 #include "gtklistbaseprivate.h"
 #include "gtklistitemfactory.h"
@@ -452,6 +453,36 @@ gtk_grid_view_get_position_from_allocation (GtkListBase           *base,
   return TRUE;
 }
 
+static GtkBitset *
+gtk_grid_view_get_items_in_rect (GtkListBase        *base,
+                                 const GdkRectangle *rect)
+{
+  GtkGridView *self = GTK_GRID_VIEW (base);
+  guint first_row, last_row, first_column, last_column, n_items;
+  GtkBitset *result;
+
+  result = gtk_bitset_new_empty ();
+
+  n_items = gtk_list_base_get_n_items (base);
+  if (n_items == 0)
+    return result;
+
+  first_column = floor (rect->x / self->column_width);
+  last_column = floor ((rect->x + rect->width) / self->column_width);
+  if (!gtk_grid_view_get_cell_at_y (self, rect->y, &first_row, NULL, NULL))
+    first_row = rect->y < 0 ? 0 : n_items - 1;
+  if (!gtk_grid_view_get_cell_at_y (self, rect->y + rect->height, &last_row, NULL, NULL))
+    last_row = rect->y < 0 ? 0 : n_items - 1;
+
+  gtk_bitset_add_rectangle (result,
+                            first_row + first_column,
+                            last_column - first_column + 1,
+                            (last_row - first_row) / self->n_columns + 1,
+                            self->n_columns);
+
+  return result;
+}
+
 static guint
 gtk_grid_view_move_focus_along (GtkListBase *base,
                                 guint        pos,
@@ -1000,6 +1031,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
   list_base_class->list_item_augment_func = cell_augment;
   list_base_class->get_allocation_along = gtk_grid_view_get_allocation_along;
   list_base_class->get_allocation_across = gtk_grid_view_get_allocation_across;
+  list_base_class->get_items_in_rect = gtk_grid_view_get_items_in_rect;
   list_base_class->get_position_from_allocation = gtk_grid_view_get_position_from_allocation;
   list_base_class->move_focus_along = gtk_grid_view_move_focus_along;
   list_base_class->move_focus_across = gtk_grid_view_move_focus_across;
index 7c4af31cb788d743d88ce997adf64d1ad68622cb..19bb54b2d26a32c6de0ffde4f6a79d58342a34fb 100644 (file)
@@ -47,7 +47,6 @@ struct _RubberbandData
   double              start_align_across;       /* alignment in horizontal direction */
   double              start_align_along;        /* alignment in vertical direction */
 
-  GtkBitset *active;
   double pointer_x, pointer_y;                  /* mouse coordinates in widget space */
   gboolean modify;
   gboolean extend;
@@ -1235,8 +1234,6 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
   gtk_widget_class_add_binding_action (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, "list.unselect-all", NULL);
 }
 
-static void gtk_list_base_update_rubberband_selection (GtkListBase *self);
-
 static gboolean
 autoscroll_cb (GtkWidget     *widget,
                GdkFrameClock *frame_clock,
@@ -1430,16 +1427,22 @@ gtk_list_base_widget_to_list (GtkListBase *self,
     }
 }
 
-void
-gtk_list_base_allocate_rubberband (GtkListBase *self)
+static GtkBitset *
+gtk_list_base_get_items_in_rect (GtkListBase        *self,
+                                 const GdkRectangle *rect)
+{
+  return GTK_LIST_BASE_GET_CLASS (self)->get_items_in_rect (self, rect);
+}
+
+static gboolean
+gtk_list_base_get_rubberband_coords (GtkListBase  *self,
+                                     GdkRectangle *rect)
 {
   GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
-  GtkRequisition min_size;
   int x1, x2, y1, y2;
-  int offset_x, offset_y;
 
   if (!priv->rubberband)
-    return;
+    return FALSE;
 
   if (priv->rubberband->start_tracker == NULL)
     {
@@ -1467,17 +1470,37 @@ gtk_list_base_allocate_rubberband (GtkListBase *self)
                                 priv->rubberband->pointer_x, priv->rubberband->pointer_y,
                                 &x2, &y2);
 
+  rect->x = MIN (x1, x2);
+  rect->y = MIN (y1, y2);
+  rect->width = ABS (x1 - x2) + 1;
+  rect->height = ABS (y1 - y2) + 1;
+
+  return TRUE;
+}
+
+void
+gtk_list_base_allocate_rubberband (GtkListBase *self)
+{
+  GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+  GtkRequisition min_size;
+  GdkRectangle rect;
+  int offset_x, offset_y;
+
+  if (!gtk_list_base_get_rubberband_coords (self, &rect))
+    return;
+
   gtk_widget_get_preferred_size (priv->rubberband->widget, &min_size, NULL);
+  rect.width = MAX (min_size.width, rect.width);
+  rect.height = MAX (min_size.height, rect.height);
 
   gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), &offset_x, NULL, NULL);
   gtk_list_base_get_adjustment_values (self, priv->orientation, &offset_y, NULL, NULL);
+  rect.x -= offset_x;
+  rect.y -= offset_y;
 
   gtk_list_base_size_allocate_child (self,
                                      priv->rubberband->widget,
-                                     MIN (x1, x2) - offset_x,
-                                     MIN (y1, y2) - offset_y,
-                                     MAX (min_size.width, ABS (x1 - x2) + 1),
-                                     MAX (min_size.height, ABS (y1 - y2) + 1));
+                                     rect.x, rect.y, rect.width, rect.height);
 }
 
 static void
@@ -1518,7 +1541,6 @@ gtk_list_base_start_rubberband (GtkListBase *self,
   priv->rubberband->widget = gtk_gizmo_new ("rubberband",
                                             NULL, NULL, NULL, NULL, NULL, NULL);
   gtk_widget_set_parent (priv->rubberband->widget, GTK_WIDGET (self));
-  priv->rubberband->active = gtk_bitset_new_empty ();
 }
 
 static void
@@ -1543,10 +1565,17 @@ gtk_list_base_stop_rubberband (GtkListBase *self)
   if (model != NULL)
     {
       GtkBitset *selected, *mask;
+      GdkRectangle rect;
+      GtkBitset *rubberband_selection;
+
+      if (!gtk_list_base_get_rubberband_coords (self, &rect))
+        return;
+
+      rubberband_selection = gtk_list_base_get_items_in_rect (self, &rect);
 
       if (priv->rubberband->extend)
         {
-          mask = gtk_bitset_ref (priv->rubberband->active);
+          mask = gtk_bitset_ref (rubberband_selection);
         }
       else
         {
@@ -1557,80 +1586,74 @@ gtk_list_base_stop_rubberband (GtkListBase *self)
       if (priv->rubberband->modify)
         selected = gtk_bitset_new_empty ();
       else
-        selected = gtk_bitset_ref (priv->rubberband->active);
+        selected = gtk_bitset_ref (rubberband_selection);
 
       gtk_selection_model_set_selection (model, selected, mask);
 
       gtk_bitset_unref (selected);
       gtk_bitset_unref (mask);
+      gtk_bitset_unref (rubberband_selection);
     }
 
   gtk_list_item_tracker_free (priv->item_manager, priv->rubberband->start_tracker);
   g_clear_pointer (&priv->rubberband->widget, gtk_widget_unparent);
-  g_clear_pointer (&priv->rubberband->active, gtk_bitset_unref);
   g_free (priv->rubberband);
   priv->rubberband = NULL;
 
   remove_autoscroll (self);
-
-  gtk_widget_queue_draw (GTK_WIDGET (self));
 }
 
 static void
-gtk_list_base_update_rubberband (GtkListBase *self,
-                                 double       x,
-                                 double       y)
+gtk_list_base_update_rubberband_selection (GtkListBase *self)
 {
   GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+  GtkListItemManagerItem *item;
+  GdkRectangle rect;
+  guint pos;
+  GtkBitset *rubberband_selection;
 
-  if (!priv->rubberband)
+  if (!gtk_list_base_get_rubberband_coords (self, &rect))
     return;
 
-  priv->rubberband->pointer_x = x;
-  priv->rubberband->pointer_y = y;
-
-  gtk_list_base_update_rubberband_selection (self);
+  rubberband_selection = gtk_list_base_get_items_in_rect (self, &rect);
 
-  update_autoscroll (self, x, y);
+  pos = 0;
+  for (item = gtk_list_item_manager_get_first (priv->item_manager);
+       item != NULL;
+       item = gtk_rb_tree_node_get_next (item))
+    {
+      if (item->widget)
+        {
+          if (gtk_bitset_contains (rubberband_selection, pos))
+            gtk_widget_set_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE, FALSE);
+          else
+            gtk_widget_unset_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE);
+        }
+      
+      pos += item->n_items;
+    }
 
-  gtk_widget_queue_draw (GTK_WIDGET (self));
+  gtk_bitset_unref (rubberband_selection);
 }
 
 static void
-gtk_list_base_update_rubberband_selection (GtkListBase *self)
+gtk_list_base_update_rubberband (GtkListBase *self,
+                                 double       x,
+                                 double       y)
 {
   GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
-  GdkRectangle rect;
-  GdkRectangle alloc;
-  GtkListItemManagerItem *item;
 
-  gtk_list_base_allocate_rubberband (self);
-  gtk_widget_get_allocation (priv->rubberband->widget, &rect);
-
-  for (item = gtk_list_item_manager_get_first (priv->item_manager);
-       item != NULL;
-       item = gtk_rb_tree_node_get_next (item))
-    {
-      guint pos;
+  if (!priv->rubberband)
+    return;
 
-      if (!item->widget)
-        continue;
+  priv->rubberband->pointer_x = x;
+  priv->rubberband->pointer_y = y;
 
-      pos = gtk_list_item_manager_get_item_position (priv->item_manager, item);
+  gtk_list_base_update_rubberband_selection (self);
 
-      gtk_widget_get_allocation (item->widget, &alloc);
+  update_autoscroll (self, x, y);
 
-      if (gdk_rectangle_intersect (&rect, &alloc, &alloc))
-        {
-          gtk_bitset_add (priv->rubberband->active, pos);
-          gtk_widget_set_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE, FALSE);
-        }
-      else
-        {
-          gtk_bitset_remove (priv->rubberband->active, pos);
-          gtk_widget_unset_state_flags (item->widget, GTK_STATE_FLAG_ACTIVE);
-        }
-    }
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
 }
 
 static void
index 87df14d2d2ff015d34a03419b837a4843c7b6a7e..c714c63c0c3d3cab3f66eed6eda742f6b9dc09c3 100644 (file)
@@ -54,6 +54,8 @@ struct _GtkListBaseClass
                                                                  int                     along,
                                                                  guint                  *pos,
                                                                  cairo_rectangle_int_t  *area);
+  GtkBitset *          (* get_items_in_rect)                    (GtkListBase            *self,
+                                                                 const cairo_rectangle_int_t *rect);
   guint                (* move_focus_along)                     (GtkListBase            *self,
                                                                  guint                   pos,
                                                                  int                     steps);
index 3bffebdeeae9abfc0a22142c4e9ec54d5403533d..4139e071c336a6b195ceb9337f5d26668615f282 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "gtklistview.h"
 
+#include "gtkbitset.h"
 #include "gtkintl.h"
 #include "gtklistbaseprivate.h"
 #include "gtklistitemmanagerprivate.h"
@@ -374,6 +375,36 @@ gtk_list_view_get_allocation_across (GtkListBase *base,
   return TRUE;
 }
 
+static GtkBitset *
+gtk_list_view_get_items_in_rect (GtkListBase                 *base,
+                                 const cairo_rectangle_int_t *rect)
+{
+  GtkListView *self = GTK_LIST_VIEW (base);
+  guint first, last, n_items;
+  GtkBitset *result;
+  ListRow *row;
+
+  result = gtk_bitset_new_empty ();
+
+  n_items = gtk_list_base_get_n_items (base);
+  if (n_items == 0)
+    return result;
+
+  row = gtk_list_view_get_row_at_y (self, rect->y, NULL);
+  if (row)
+    first = gtk_list_item_manager_get_item_position (self->item_manager, row);
+  else
+    first = rect->y < 0 ? 0 : n_items - 1;
+  row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, NULL);
+  if (row)
+    last = gtk_list_item_manager_get_item_position (self->item_manager, row);
+  else
+    last = rect->y < 0 ? 0 : n_items - 1;
+
+  gtk_bitset_add_range_closed (result, first, last);
+  return result;
+}
+
 static guint
 gtk_list_view_move_focus_along (GtkListBase *base,
                                 guint        pos,
@@ -773,6 +804,7 @@ gtk_list_view_class_init (GtkListViewClass *klass)
   list_base_class->list_item_augment_func = list_row_augment;
   list_base_class->get_allocation_along = gtk_list_view_get_allocation_along;
   list_base_class->get_allocation_across = gtk_list_view_get_allocation_across;
+  list_base_class->get_items_in_rect = gtk_list_view_get_items_in_rect;
   list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
   list_base_class->move_focus_along = gtk_list_view_move_focus_along;
   list_base_class->move_focus_across = gtk_list_view_move_focus_across;